using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NFreeMarker.Log;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Ext.Beans
{
    /**
     * Wraps the static fields and methods of a class in a
     * {@link freemarker.template.TemplateHashModel}.
     * Fields are wrapped using {@link BeansWrapper#wrap(Object)}, and
     * methods are wrapped into an appropriate {@link freemarker.template.TemplateMethodModelEx} instance.
     * Unfortunately, there is currently no support for bean property-style
     * calls of static methods, similar to that in {@link BeanModel}.
     * @author Attila Szegedi
     * @version $Id: StaticModel.java,v 1.24.2.1 2006/11/12 10:23:02 szegedia Exp $
     */
    class StaticModel : ITemplateHashModelEx
    {
        private static readonly Logger Logger = Logger.GetLogger("freemarker.beans");
        private readonly Type _type;
        private readonly BeansWrapper _wrapper;
        private readonly Dictionary<string, object> _map = new Dictionary<string, object>();

        internal StaticModel(Type type, BeansWrapper wrapper)
        {
            _type = type;
            _wrapper = wrapper;
            Populate();
        }

        /**
         * Returns the field or method named by the <tt>key</tt>
         * parameter.
         */
        public ITemplateModel Get(string key)
        {
            object model = _map.GetValueOrDefault(key);
            // Simple method, overloaded method or final field -- these have cached 
            // template models
            if (model is ITemplateModel)
            {
                return (ITemplateModel) model;
            }
            // Non-final field; this must be evaluated on each call.
            if (model is FieldInfo)
            {
                //try
                //{
                //    return _wrapper.GetOuterIdentity().Wrap(((FieldInfo) model).GetValue(null));
                //}
                //catch (IllegalAccessException)
                //{
                //    throw new TemplateModelException(
                //        "Illegal access for field " + key + " of class " + _type.FullName);
                //}
                throw new NotImplementedException();
            }

            throw new TemplateModelException(
                "No such key: " + key + " in class " + _type.FullName);
        }

        /**
         * Returns true if there is at least one public static
         * field or method in the underlying class.
         */
        public bool IsEmpty()
        {
            return _map.Count == 0;
        }

        public int Size()
        {
            return _map.Count;
        }
        
        public ITemplateCollectionModel Keys()
        {
            return (ITemplateCollectionModel) _wrapper.GetOuterIdentity().Wrap(_map.Keys);
        }
        
        public ITemplateCollectionModel Values()
        {
            return (ITemplateCollectionModel) _wrapper.GetOuterIdentity().Wrap(_map.Values);
        }

        private void Populate()
        {
            if (!_type.IsPublic)
            {
                throw new TemplateModelException(
                    "Can't wrap the non-public class " + _type.FullName);
            }
            
            if (_wrapper.GetExposureLevel() == BeansWrapper.ExposeNothing)
            {
                return;
            }

            FieldInfo[] fields = _type.GetFields();
            foreach (FieldInfo field in fields)
            {
                if (field.IsPublic && field.IsStatic)
                {
                    if (field.IsInitOnly)
                    {
                        //try
                        //{
                        //    // public static final fields are evaluated once and
                        //    // stored in the map
                        //    _map.Add(field.Name, _wrapper.GetOuterIdentity().Wrap(field.GetValue(null)));
                        //}
                        //catch (IllegalAccessException)
                        //{
                        //    // Intentionally ignored
                        //}
                        throw new NotImplementedException();
                    }
                    else
                    {
                        // This is a special flagging value: Field in the map means
                        // that this is a non-final field, and it must be evaluated
                        // on each get() call.
                        _map.Add(field.Name, field);
                    }
                }
            }
            if (_wrapper.GetExposureLevel() < BeansWrapper.ExposePropertiesOnly)
            {
                MethodInfo[] methods = _type.GetMethods();
                foreach (MethodInfo method in methods)
                {
                    if (method.IsPublic && method.IsStatic && _wrapper.IsSafeMethod(method))
                    {
                        string name = method.Name;
                        object obj = _map.GetValueOrDefault(name);
                        if (obj is MethodInfo)
                        {
                            var methodMap = new MethodMap(name, _wrapper);
                            methodMap.AddMember((MethodInfo) obj);
                            methodMap.AddMember(method);
                            _map.Add(name, methodMap);
                        }
                        else if (obj is MethodMap)
                        {
                            var methodMap = (MethodMap) obj;
                            methodMap.AddMember(method);
                        }
                        else
                        {
                            if (obj != null)
                            {
                                Logger.Info("Overwriting value [" + obj + "] for " +
                                           " key '" + name + "' with [" + method +
                                           "] in static model for " + _type.Name);
                            }
                            _map.Add(name, method);
                        }
                    }
                }
                foreach (KeyValuePair<string, object> pair in _map.ToList())
                {
                    object value = pair.Value;
                    if (value is MethodInfo)
                    {
                        var method = (MethodInfo) value;
                        _map[pair.Key] = new SimpleMethodModel(null, method,
                                method.GetParameterTypes(), _wrapper);
                    }
                    else if (value is MethodMap)
                    {
                        _map[pair.Key] = new OverloadedMethodModel(null, 
                                (MethodMap)value);
                    }
                }
            }
        }
    }
}